// The libMesh Finite Element Library.
// Copyright (C) 2002-2021 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

// Local includes
#include "libmesh/face_tri.h"
#include "libmesh/edge_edge2.h"
#include "libmesh/face_tri3.h"
#include "libmesh/enum_elem_quality.h"

// C++ includes
#include <array>

namespace libMesh
{


// ------------------------------------------------------------
// Tri class static member initializations


// We need to require C++11...
const Real Tri::_master_points[6][3] =
  {
    {0, 0},
    {1, 0},
    {0, 1},
    {0.5, 0},
    {0.5, 0.5},
    {0, 0.5}
  };







// ------------------------------------------------------------
// Tri class member functions
dof_id_type Tri::key (const unsigned int s) const
{
  libmesh_assert_less (s, this->n_sides());

  return this->compute_key(this->node_id(Tri3::side_nodes_map[s][0]),
                           this->node_id(Tri3::side_nodes_map[s][1]));
}



unsigned int Tri::local_side_node(unsigned int side,
                                  unsigned int side_node) const
{
  libmesh_assert_less (side, this->n_sides());
  libmesh_assert_less (side_node, Tri3::nodes_per_side);

  return Tri3::side_nodes_map[side][side_node];
}



unsigned int Tri::local_edge_node(unsigned int edge,
                                  unsigned int edge_node) const
{
  return local_side_node(edge, edge_node);
}



dof_id_type Tri::key () const
{
  return this->compute_key(this->node_id(0),
                           this->node_id(1),
                           this->node_id(2));
}



std::unique_ptr<Elem> Tri::side_ptr (const unsigned int i)
{
  libmesh_assert_less (i, this->n_sides());

  std::unique_ptr<Elem> edge = libmesh_make_unique<Edge2>();

  for (auto n : edge->node_index_range())
    edge->set_node(n) = this->node_ptr(Tri3::side_nodes_map[i][n]);

  return edge;
}



void Tri::side_ptr (std::unique_ptr<Elem> & side,
                    const unsigned int i)
{
  this->simple_side_ptr<Tri,Tri3>(side, i, EDGE2);
}



bool Tri::is_child_on_side(const unsigned int c,
                           const unsigned int s) const
{
  libmesh_assert_less (c, this->n_children());
  libmesh_assert_less (s, this->n_sides());

  return (c == s || c == (s+1)%3);
}



Real Tri::quality (const ElemQuality q) const
{
  switch (q)
    {

      /**
       * Source: Netgen, meshtool.cpp, TriangleQualityInst
       */
    case DISTORTION:
    case STRETCH:
      {
        const Point & p1 = this->point(0);
        const Point & p2 = this->point(1);
        const Point & p3 = this->point(2);

        Point v1 = p2 - p1;
        Point v2 = p3 - p1;
        Point v3 = p3 - p2;
        const Real l1 = v1.norm();
        const Real l2 = v2.norm();
        const Real l3 = v3.norm();

        // if one length is 0, quality is quite bad!
        if ((l1 <=0.) || (l2 <= 0.) || (l3 <= 0.))
          return 0.;

        const Real s1 = std::sin(std::acos(v1*v2/l1/l2)/2.);
        v1 *= -1;
        const Real s2 = std::sin(std::acos(v1*v3/l1/l3)/2.);
        const Real s3 = std::sin(std::acos(v2*v3/l2/l3)/2.);

        return 8. * s1 * s2 * s3;

      }

      // From: P. Knupp, "Algebraic mesh quality metrics for
      // unstructured initial meshes," Finite Elements in Analysis
      // and Design 39, 2003, p. 217-241, Section 3.2.
    case SHAPE:
      {
        // Unlike Quads, the Tri SHAPE metric is independent of the
        // node at which it is computed, we choose to compute it for
        // node 0.

        // The nodal Jacobian matrix A is a 3x2 matrix, hence we
        // represent it by a std:array with 6 entries.
        Point
          d01 = point(1) - point(0),
          d02 = point(2) - point(0);

        std::array<Real, 6> A =
          {{d01(0), d02(0),
            d01(1), d02(1),
            d01(2), d02(2)}};

        // Compute metric tensor entries, T = A^T * A.
        // This is a symmetric 2x2 matrix so we only
        // compute one of the off-diagonal entries.
        // As in the paper, we define lambda_ij := T_ij.
        Real
          lambda11 = A[0]*A[0] + A[2]*A[2] + A[4]*A[4],
          lambda12 = A[0]*A[1] + A[2]*A[3] + A[4]*A[5],
          lambda22 = A[1]*A[1] + A[3]*A[3] + A[5]*A[5];

        // Compute the denominator of the metric. If it is exactly
        // zero then return 0 (lowest quality) for this metric.
        Real den = lambda11 + lambda22 - lambda12;
        if (den == 0.0)
          return 0.;

        // Compute the nodal area
        Real alpha = std::sqrt(lambda11 * lambda22 - lambda12 * lambda12);

        // Finally, compute and return the metric.
        return std::sqrt(3) * alpha / den;
      }

    default:
      return Elem::quality(q);
    }

  // We won't get here.
  return Elem::quality(q);
}






std::pair<Real, Real> Tri::qual_bounds (const ElemQuality q) const
{
  std::pair<Real, Real> bounds;

  switch (q)
    {

    case MAX_ANGLE:
      bounds.first  = 60.;
      bounds.second = 90.;
      break;

    case MIN_ANGLE:
      bounds.first  = 30.;
      bounds.second = 60.;
      break;

    case CONDITION:
      bounds.first  = 1.;
      bounds.second = 1.3;
      break;

    case JACOBIAN:
      bounds.first  = 0.5;
      bounds.second = 1.155;
      break;

    case SIZE:
    case SHAPE:
      bounds.first  = 0.25;
      bounds.second = 1.;
      break;

    case DISTORTION:
      bounds.first  = 0.6;
      bounds.second = 1.;
      break;

    default:
      libMesh::out << "Warning: Invalid quality measure chosen." << std::endl;
      bounds.first  = -1;
      bounds.second = -1;
    }

  return bounds;
}

} // namespace libMesh
